package com.java.compilation;

import com.java.compilation.helper.Keyword;
import com.java.compilation.helper.TokenType;
import com.java.compilation.helper.XmlWriter;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class CompilationEngine_10 {
    private final JackTokenizer tokenizer;
    private final XmlWriter xmlWriter;
    // 制表符tab
    private int tSize;
    private static final Set<String> opSymbols = new HashSet<>();

    public CompilationEngine_10(JackTokenizer tokenizer, File xmlFile) throws IOException {
        this.tokenizer = tokenizer;
        opSymbols.addAll(Arrays.asList("+", "-", "*", "/", "&", "|", "<", ">", "="));
        xmlWriter = new XmlWriter(xmlFile);

        readNextToken();
        // should class
        shouldTokenEquals(TokenType.KEYWORD, Keyword.CLASS.name());
        outputStartLabel("<class>");
        compileClass();
        outputEndLabel("</class>");
    }

    public void compileClass() throws IOException {
        // 'class' className '{' classVarDec* subroutineDec* '}'
        outputToken();

        // className 标识符
        readNextToken();
        shouldTokenEquals(TokenType.IDENTIFIER);
        outputToken();

        // '{'
        readNextToken();
        shouldTokenEquals(TokenType.SYMBOL, "{");
        outputToken();

        // classVarDec* or subroutineDec*
        while (tokenizer.hasMoreTokens()) {
            readNextToken();
            if (tokenizer.tokenType() == TokenType.KEYWORD) {
                // classVarDec static field
                if (Keyword.STATIC == tokenizer.keyword() || Keyword.FIELD == tokenizer.keyword()) {
                    outputStartLabel("<classVarDec>");
                    compileClassVarDec();
                    outputEndLabel("</classVarDec>");
                } else if (Keyword.CONSTRUCTOR == tokenizer.keyword() || Keyword.FUNCTION == tokenizer.keyword()
                        || Keyword.METHOD == tokenizer.keyword()) {
                    // subroutineDec
                    outputStartLabel("<subroutineDec>");
                    compileSubroutine();
                    outputEndLabel("</subroutineDec>");
                }
            } else {
                // '}'
                shouldTokenEquals(TokenType.SYMBOL, "}");
                outputToken();
            }
        }
    }

    public void compileClassVarDec() throws IOException {
        // ('static'|'field') type varName (',' varName)* ';'
        outputToken();

        // type
        readNextToken();
        outputToken();

        // varName
        readNextToken();
        outputToken();

        readNextToken();
        while (tokenizer.tokenType() != TokenType.SYMBOL && ",".equalsIgnoreCase(tokenizer.symbol())) {
            // ','
            outputToken();

            // varName
            readNextToken();
            outputToken();

            readNextToken();
        }
        outputToken();
    }

    public void compileSubroutine() throws IOException {
        // ('constructor'|'function'|'method'') ('void'|type) subroutineName '(' parameterList ')' subroutineBody
        outputToken();

        // ('void'|type)
        readNextToken();
        outputToken();

        // subroutineName
        readNextToken();
        outputToken();

        // '('
        readNextToken();
        shouldTokenEquals(TokenType.SYMBOL, "(");
        outputToken();

        readNextToken();
        compileParameterList();

        // ')'
        outputToken();

        // subroutineBody
        outputStartLabel("<subroutineBody>");
        compileSubroutineBody();
        outputEndLabel("</subroutineBody>");
    }

    private void compileSubroutineBody() throws IOException {
        // '{' varDec* statements '}'
        readNextToken();
        shouldTokenEquals(TokenType.SYMBOL, "{");
        outputToken();

        // 读一个token，
        readNextToken();
        while (tokenizer.tokenType() == TokenType.KEYWORD && Keyword.VAR == tokenizer.keyword()) {
            compileVarDec();

            readNextToken();
        }

        outputStartLabel("<statements>");
        compileStatements();
        outputEndLabel("</statements>");

        shouldTokenEquals(TokenType.SYMBOL, "}");
        outputToken();
    }

    public void compileParameterList() throws IOException {
//        if (tokenizer.tokenType() == TokenType.SYMBOL && tokenizer.symbol().equalsIgnoreCase(")")) {
//            return;
//        }

        outputStartLabel("<parameterList>");

        // (((type varName)(',' type varName))*)?
        while (tokenizer.tokenType() != TokenType.SYMBOL || !")".equalsIgnoreCase(tokenizer.symbol())) {
            // type
            outputToken();

            // varName
            readNextToken();
            outputToken();

            // 读下一个token
            readNextToken();
        }

        outputEndLabel("</parameterList>");

    }

    public void compileVarDec() throws IOException {
        outputStartLabel("<varDec>");

        // 'var' type varName (',' varName)* ';'
        outputToken();

        // type
        readNextToken();
        outputToken();

        // varName
        readNextToken();
        outputToken();

        readNextToken();
        while (tokenizer.tokenType() == TokenType.SYMBOL && ",".equalsIgnoreCase(tokenizer.symbol())) {
            outputToken();

            // varName
            readNextToken();
            outputToken();

            readNextToken();
        }

        // ';'
        shouldTokenEquals(TokenType.SYMBOL, ";");
        outputToken();

        outputEndLabel("</varDec>");
    }

    public void compileStatements() throws IOException {
        // statement*
        while (true) {
            if (tokenizer.tokenType() == TokenType.KEYWORD && Keyword.LET == tokenizer.keyword()) {
                // letStatement
                outputStartLabel("<letStatement>");
                compileLet();
                outputEndLabel("</letStatement>");

            } else if (tokenizer.tokenType() == TokenType.KEYWORD && Keyword.IF == tokenizer.keyword()) {
                // ifStatement
                outputStartLabel("<ifStatement>");
                compileIf();
                outputEndLabel("</ifStatement>");
            } else if (tokenizer.tokenType() == TokenType.KEYWORD && Keyword.WHILE == tokenizer.keyword()) {
                // whileStatement
                outputStartLabel("<whileStatement>");
                compileWhile();
                outputEndLabel("</whileStatement>");
            } else if (tokenizer.tokenType() == TokenType.KEYWORD && Keyword.DO == tokenizer.keyword()) {
                // doStatement
                outputStartLabel("<doStatement>");
                compileDo();
                outputEndLabel("</doStatement>");
            } else if (tokenizer.tokenType() == TokenType.KEYWORD && Keyword.RETURN == tokenizer.keyword()) {
                // returnStatement
                outputStartLabel("<returnStatement>");
                compileReturn();
                outputEndLabel("</returnStatement>");
            } else {
                break;
            }
            readNextToken();
        }
    }

    public void compileDo() throws IOException {
        // 'do' subroutineCall ';'
        outputToken();

        // subroutineName
        readNextToken();
        outputToken();

        readNextToken();
        subroutineCall();

        // ';'
        readNextToken();
        outputToken();
    }

    public void compileLet() throws IOException {
        // 'let' varName ('['expression']')?'='expression';'
        outputToken();

        // varName
        readNextToken();
        outputToken();

        readNextToken();
        // 数组处理
        if (tokenizer.tokenType() == TokenType.SYMBOL && "[".equalsIgnoreCase(tokenizer.symbol())) {
            outputToken();

            readNextToken();
            outputStartLabel("<expression>");
            compileExpression();
            outputEndLabel("</expression>");

            // ']'
//            readNextToken();
            shouldTokenEquals(TokenType.SYMBOL, "]");
            outputToken();

            readNextToken();
        }

        // '='
        outputToken();

        readNextToken();
        // expression
        outputStartLabel("<expression>");
        compileExpression();
        outputEndLabel("</expression>");

//        readNextToken();
        shouldTokenEquals(TokenType.SYMBOL, ";");
        outputToken();
    }

    public void compileWhile() throws IOException {
        // 'while' '(' expression ')' '{' statements '}'
        outputToken();

        // '('
        readNextToken();
        outputToken();

        readNextToken();
        outputStartLabel("<expression>");
        compileExpression();
        outputEndLabel("</expression>");

        // ')'
        outputToken();

        // '{'
        readNextToken();
        outputToken();

        readNextToken();
        outputStartLabel("<statements>");
        compileStatements();
        outputEndLabel("</statements>");

        shouldTokenEquals(TokenType.SYMBOL, "}");
        outputToken();
    }

    public void compileReturn() throws IOException {
        // 'return' expression ';'
        outputToken();

        readNextToken();
        if (tokenizer.tokenType() != TokenType.SYMBOL || !";".equalsIgnoreCase(tokenizer.symbol())) {
            outputStartLabel("<expression>");
            compileExpression();
            outputEndLabel("</expression>");
        }

        // ';'
//        readNextToken();
        outputToken();
    }

    public void compileIf() throws IOException {
        // 'if' '('expression')''{'statements'}'
        // ('else' '{'statements'}')?
        outputToken();

        // '('
        readNextToken();
        outputToken();

        readNextToken();
        outputStartLabel("<expression>");
        compileExpression();
        outputEndLabel("</expression>");

        // ')'
        outputToken();

        // '{'
        readNextToken();
        outputToken();

        readNextToken();
        outputStartLabel("<statements>");
        compileStatements();
        outputEndLabel("</statements>");

        // '}'
        outputToken();
    }

    public void compileExpression() throws IOException {
        // term (op term)*
        compileTerm();

        // (op term)*
        while (tokenizer.tokenType() == TokenType.SYMBOL && opSymbols.contains(tokenizer.symbol())) {
            // op
            outputToken();

            readNextToken();
            compileTerm();
        }
    }

    public void compileTerm() throws IOException {
        outputStartLabel("<term>");
        // integerConstant | stringConstant | keywordConstant | varName | varName '[' expression ']' | subroutineCall |
        // '('expression ')' | unaryOp term
        //  integerConstant | stringConstant | keywordConstant
        if (tokenizer.tokenType() == TokenType.INT_CONST
                || tokenizer.tokenType() == TokenType.STRING_CONST
                || tokenizer.tokenType() == TokenType.KEYWORD) {
            outputToken();
            readNextToken();

            outputEndLabel("</term>");
            return;
        }
        // '('expression ')'
        if (tokenizer.tokenType() == TokenType.SYMBOL && "(".equalsIgnoreCase(tokenizer.symbol())) {
            // '('
            outputToken();

            readNextToken();
            outputStartLabel("<expression>");
            compileExpression();
            outputEndLabel("</expression>");

            // ')'
            outputToken();

            readNextToken();
            outputEndLabel("</term>");
            return;
        }

        // varName | varName '[' expression ']' | subroutineCall
        if (tokenizer.tokenType() == TokenType.IDENTIFIER) {
            outputToken();

            readNextToken();
            if (tokenizer.tokenType() == TokenType.SYMBOL && "[".equalsIgnoreCase(tokenizer.symbol())) {
                // '['
                outputToken();
                readNextToken();
                // 数组处理
                outputStartLabel("<expression>");
                compileExpression();
                outputEndLabel("</expression>");

                // ']'
                shouldTokenEquals(TokenType.SYMBOL, "]");
                outputToken();

                readNextToken();
            } else if (tokenizer.tokenType() == TokenType.SYMBOL
                    && ("(".equalsIgnoreCase(tokenizer.symbol())
                    || ".".equalsIgnoreCase(tokenizer.symbol()))) {
                subroutineCall();

                readNextToken();
            }

            outputEndLabel("</term>");
            return;
        }
        if (tokenizer.tokenType() == TokenType.SYMBOL) {
            outputToken();

            readNextToken();


            return;
        }

//        if ()

        // subroutineCall
        outputToken();

        outputEndLabel("</term>");
    }

    private void subroutineCall() throws IOException {
        // subroutineName '(' expressionList ')' | (className | varName)'.' subroutineName '(' expressionList ')'

        if (tokenizer.tokenType() == TokenType.SYMBOL && ".".equalsIgnoreCase(tokenizer.symbol())) {
            // (className | varName)'.' subroutineName '(' expressionList ')'
            // '.'
            outputToken();
            // subroutineName
            readNextToken();
            outputToken();

            readNextToken();
        }
        // '('
        outputToken();

        // expressionList
        outputStartLabel("<expressionList>");
        compileExpressionList();
        outputEndLabel("</expressionList>");

        // ')'
        outputToken();
    }

    public void compileExpressionList() throws IOException {
        readNextToken();
        if (tokenizer.tokenType() == TokenType.SYMBOL && ")".equalsIgnoreCase(tokenizer.symbol())) {
            return;
        }

        // (expression(','expression)*)?
        outputStartLabel("<expression>");
        compileExpression();
        outputEndLabel("</expression>");


        // (','expression)*
        while (tokenizer.tokenType() == TokenType.SYMBOL && ",".equalsIgnoreCase(tokenizer.symbol())) {
            outputToken();

            readNextToken();
            outputStartLabel("<expression>");
            compileExpression();
            outputEndLabel("</expression>");

        }
    }

    private void shouldTokenEquals(TokenType tokenType) {
        if (tokenizer.tokenType() != tokenType) {
            throw new IllegalArgumentException(String.format("token type not equals %s", tokenType));
        }
    }

    private void shouldTokenEquals(TokenType tokenType, String value) {
        shouldTokenEquals(tokenType);

        if (tokenType == TokenType.KEYWORD && !tokenizer.keyword().name().equalsIgnoreCase(value)) {
            throw new IllegalArgumentException(String.format("keyword not equals %s", value));
        } else if (tokenType == TokenType.SYMBOL && !value.equalsIgnoreCase(tokenizer.symbol())) {
            throw new IllegalArgumentException(String.format("symbol not equals %s", value));
        }
    }

    private void readNextToken() throws IOException {
        if (!tokenizer.hasMoreTokens()) {
            throw new IllegalArgumentException("read token error");
        }
        tokenizer.advance();
    }

    private void outputStartLabel(String label) {
        outputTab();
        xmlWriter.println(label);
        tSize++;
    }

    private void outputEndLabel(String label) {
        tSize--;
        outputTab();
        xmlWriter.println(label);
    }

    private void outputToken() {
        outputTab();
        xmlWriter.println(tokenizer);
    }

    private void outputTab() {
        for (int i = 0; i < tSize; i++) {
            xmlWriter.print("\t");
        }
    }

}
